import operator
import os
import random
import time
from typing import List, Dict, TypedDict, Annotated

from dotenv import load_dotenv
from langchain_core.messages import HumanMessage
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, END

# 加载环境变量
load_dotenv()

# ---------------- 配置区域 ----------------
# 强烈建议使用 GPT-4o，推理能力对裁判打分和复杂博弈至关重要
try:
    llm = ChatOpenAI(
        model=os.getenv("LONGMAO_MODEL", "gpt-4o"),
        base_url=os.getenv("LONGMAO_BASE_URL"),
        api_key=os.getenv("LONGMAO_API_KEY"),
        temperature=0.5  # 稍微提高一点温度，增加 AI 性格的多样性表现
    )
except Exception as e:
    print(f"Error initializing LLM: {e}")
    exit(1)

# 真人玩家的名字
HUMAN_NAME = "我(真人)"

# AI 性格定义
PERSONALITY_RADICAL = "激进派"  # 极具攻击性，容忍度低，带节奏
PERSONALITY_CONSERVATIVE = "保守派"  # 谨慎，随大流，求稳
PERSONALITY_RATIONAL = "理智派"  # (仅平民) 基于数据和逻辑，平衡风格


# ---------------- 1. 定义游戏状态 ----------------
class GameState(TypedDict):
    players: List[str]
    player_words: Dict[str, str]
    spy_player: str
    # 【新增】存储每个 AI 的性格
    player_personalities: Dict[str, str]
    history: Annotated[List[str], operator.add]
    current_turn_idx: int
    round_count: int
    votes: Dict[str, str]
    game_result: str
    cheater_detected: str
    judge_scores: Dict[str, str]


# ---------------- 2. 核心节点逻辑 ----------------

def init_game_node(state: GameState):
    """上帝节点：发牌与性格分配 (4人局)"""
    print("\n" + "=" * 40)
    print("🎮 游戏开始！4人局，一票定生死！")
    word_pairs = [
        ("苹果", "梨"), ("微信", "QQ"), ("麦当劳", "肯德基"),
        ("成吉思汗", "努尔哈赤"), ("蜘蛛侠", "蝙蝠侠"), ("程序员", "黑客"),
        ("吉他", "小提琴"), ("足球", "篮球")
    ]
    common_word, spy_word = random.choice(word_pairs)

    # 定义玩家 (1个真人 + 3个AI)
    ai_players = ["AI_1号", "AI_2号", "AI_3号"]
    players = [HUMAN_NAME] + ai_players
    random.shuffle(players)
    spy = random.choice(players)

    # 分配词汇
    allocation = {}
    for p in players:
        allocation[p] = spy_word if p == spy else common_word

    # 【新增】分配性格
    personalities = {}

    # 1. 确定卧底性格 (如果卧底是AI)
    if spy in ai_players:
        personalities[spy] = random.choice([PERSONALITY_RADICAL, PERSONALITY_CONSERVATIVE])

    # 2. 确定平民AI性格
    civ_ais = [p for p in ai_players if p != spy]
    # 确保至少有一个激进派平民
    num_radical = random.randint(1, len(civ_ais))
    num_rational = len(civ_ais) - num_radical

    civ_personalities = [PERSONALITY_RADICAL] * num_radical + [PERSONALITY_RATIONAL] * num_rational
    random.shuffle(civ_personalities)

    for i, ai_civ in enumerate(civ_ais):
        personalities[ai_civ] = civ_personalities[i]

    # 打印信息
    human_word = allocation[HUMAN_NAME]
    identity = '卧底' if spy == HUMAN_NAME else '平民'
    print(f"🤫 嘘！你的身份是【{identity}】")
    print(f"🔑 你的词是：【{human_word}】")
    # print(f"(调试：AI性格分布: {personalities})") # 调试用，实际游戏可注释
    print("-" * 40)
    print(f"👥 玩家发言顺序：{' -> '.join(players)}")
    print("=" * 40 + "\n")
    time.sleep(1)
    return {
        "players": players,
        "player_words": allocation,
        "spy_player": spy,
        "player_personalities": personalities,
        "history": [],
        "current_turn_idx": 0,
        "round_count": 1,
        "votes": {},
        "cheater_detected": None,
        "judge_scores": {}
    }


def speaker_node(state: GameState):
    """发言节点 (保持不变)"""
    current_player = state["players"][state["current_turn_idx"]]
    my_word = state["player_words"][current_player]
    spy_player = state["spy_player"]
    round_num = state["round_count"]
    history_str = "\n".join(state['history']) if state['history'] else "（暂无，你是首个发言）"

    print(f"👉 轮到 [{current_player}] 发言...")

    if current_player == HUMAN_NAME:
        print(f"   (提示: 你的词是【{my_word}】)")
        while True:
            content = input(f"🎤 请输入你的描述 > ").strip()
            if not content: continue
            if my_word in content:
                print(f"🚫 犯规警告！不能直接包含关键词。请重试。")
                continue
            break
        msg = f"[{current_player}]: {content}"
    else:
        is_spy = (current_player == spy_player)
        if is_spy:
            prompt = f"""
                        你是{current_player}，身份【卧底】，词【{my_word}】。
                        目标：隐藏自己。第 {round_num} 轮。
                        历史发言（包含上一轮投票理由）：
                        {history_str}
                        思维链：1.风险评估：结合历史发言和上一轮大家的投票理由，判断自己现在的处境。2.伪装策略：描述真实特征，严禁使用独有特征。
                        输出：仅一句话描述。
                        """
        else:
            prompt = f"""
            你是{current_player}，身份【平民】，词【{my_word}】。
            目标：找卧底。第 {round_num} 轮。
            历史发言（包含上一轮投票理由）：
            {history_str}
            思维链：1.分析局势：结合历史发言和上一轮大家的投票理由，寻找可疑点。2.发言策略：描述一个相对核心特征向队友示好。
            输出：仅一句话描述。
            """
        try:
            response = llm.invoke([HumanMessage(content=prompt)])
            content = response.content.replace(my_word, "***").strip()
            if "最终输出" in content: content = content.split("最终输出")[-1].strip(": \n")
            msg = f"[{current_player}]: {content}"
            print(f"🤖 {msg}")
            time.sleep(random.uniform(0.5, 1.0))
        except Exception as e:
            msg = f"[{current_player}]: (大脑过载...跳过)"
            print(f"❌ AI生成失败: {e}")
    return {"history": [msg]}


def fact_check_node(state: GameState):
    """防作弊节点 (保持不变)"""
    current_player = state["players"][state["current_turn_idx"]]
    last_msg = state["history"][-1]
    real_word = state["player_words"][current_player]
    spoken_text = last_msg.split(":", 1)[1].strip() if ":" in last_msg else last_msg

    prompt = f"""
    裁判任务。词：【{real_word}】。描述："{spoken_text}"
    判断是否违规（FAIL）：1.撒谎/事实错误。2.直接包含词汇或过于直白导致游戏失去意义。
    模糊描述属于正常（PASS）。只回答：PASS 或 FAIL
    """
    try:
        check_res = llm.invoke([HumanMessage(content=prompt)]).content.strip().upper()
        if "FAIL" in check_res:
            print(f"\n🚨 裁判红牌！[{current_player}] 严重违规！直接淘汰！\n")
            return {"cheater_detected": current_player}
    except:
        pass
    return {"current_turn_idx": state["current_turn_idx"] + 1}


# ==================== LLM 评分节点 V3.0 (上帝视角) ====================
def llm_scoring_node(state: GameState):
    """LLM 评分节点 V3.0：上帝视角裁判"""
    print("\n🧑‍🏫 === 正在等待裁判团进行语义评估(上帝视角) ===")
    num_players = len(state["players"])
    current_turn_history = state["history"][-num_players:]
    spy_player = state["spy_player"]
    if spy_player != HUMAN_NAME:
        common_word = state["player_words"][HUMAN_NAME]
        spy_word = state["player_words"][spy_player]
    else:
        common_word = state["player_words"]["AI_1号"]  # 简单取一个AI的词
        spy_word = state["player_words"][HUMAN_NAME]
    print(f"   (裁判内幕：平民词【{common_word}】 vs 卧底词【{spy_word}】)")

    scores_report = {}
    for msg in current_turn_history:
        player_name, content = msg.split(":", 1)
        player_name = player_name.strip("[]")
        content = content.strip()
        prompt = f"""
        你是一位《谁是卧底》游戏的专业裁判。
        绝密内幕信息（玩家不可见）：标准平民词【{common_word}】，干扰卧底词【{spy_word}】。
        玩家 [{player_name}] 的描述是：“{content}”
        请打分（0-100）并给出简短评语。
        评分标准：以与【{common_word}】契合度为基准。若描述侧重点明显偏向【{spy_word}】的核心独有特征，必须重罚（低分）！极度划水也低分。
        【绝对安全禁令】：严禁在评语中直接提及【{spy_word}】！泄密直接判负。
        输出格式：`分数|简短评语(15字内，不可泄密)`
        """
        try:
            res = llm.invoke([HumanMessage(content=prompt)])
            result_str = res.content.strip()
            if "|" not in result_str: result_str = "50|裁判未给出具体评价"
            score_val = int(result_str.split("|")[0])
            comment = result_str.split("|")[1]
            scores_report[player_name] = result_str
            bar_len = int(score_val / 5)
            bar = "█" * bar_len + "░" * (20 - bar_len)
            print(f"   📝 [{player_name}] 得分: {score_val:3d} [{bar}] 评语: {comment}")
        except Exception as e:
            print(f"   ⚠️ 裁判对 [{player_name}] 评分失败: {e}")
            scores_report[player_name] = "50|评分失败"
    print("============================================\n")
    return {"judge_scores": scores_report}


# ==================== 投票节点 V8.0 (人格博弈与舆论战) ====================
def voting_node(state: GameState):
    """投票节点 V8.0：引入性格差异，并将投票理由记入历史"""
    print(f"\n🗳️ === 第 {state['round_count']} 轮描述结束，开始投票 ===")
    print("-" * 30)
    players = state["players"]
    spy_player = state["spy_player"]
    personalities = state["player_personalities"]
    # 历史记录包含之前的发言和投票理由
    history_str = "\n".join(state["history"])
    scores_report = state.get("judge_scores", {})
    report_str = "\n".join([f"- {name}: {score}" for name, score in scores_report.items()])

    new_votes = {}
    vote_log = []  # 用于记录本轮投票情况，稍后加入历史

    for p in players:
        if p == HUMAN_NAME:
            # (真人投票逻辑不变)
            print("💀 请根据上面的历史发言，找出卧底。(4人局，投错平民即输！)")
            print(f"   候选人: {players} (如果不确定，可以输入'弃票')")
            while True:
                vote_target = input(f"🗳️ 你投给谁? > ").strip()
                if vote_target == "弃票": break
                found = False
                for valid in players:
                    if vote_target and (valid in vote_target or vote_target in valid):
                        vote_target = valid
                        found = True
                        break
                if found: break
                print("❌ 找不到该玩家，请重新输入(或输入'弃票')。")

            reason = "真人玩家投票"  # 简单记录
            if vote_target == "弃票":
                print(f"✅ 你选择了: 弃票")
                vote_log.append(f"[{p}] 选择弃票。")
            else:
                print(f"✅ 你投给了: {vote_target}")
                # 这里可以扩展让真人输入理由，为了流畅暂时省略
                vote_log.append(f"[{p}] 投给了 {vote_target}。")

        else:
            # AI 投票
            my_word = state["player_words"][p]
            is_spy = (p == spy_player)
            personality = personalities.get(p, PERSONALITY_RATIONAL)

            # 根据身份和性格选择不同的 Prompt
            if is_spy:
                if personality == PERSONALITY_RADICAL:
                    # === 激进派卧底：疯狗模式 ===
                    prompt = f"""
                    你是{p}，【卧底】(词【{my_word}】)，性格【{personality}】。
                    目标：极具攻击性，带节奏，把水搅浑。哪怕平民表现完美，也要硬找出刺来攻击。
                    历史与裁判报告已给出。
                    思维链：寻找一个最有威胁或最容易带节奏的平民目标。利用历史发言或裁判评语，编造一个极具煽动性的理由进行诬陷攻击。绝不弃票！
                    输出：`被投人名字|极具攻击性的简短理由(基于歪曲事实)`
                    """
                else:
                    # === 保守派卧底：老六模式 ===
                    prompt = f"""
                    你是{p}，【卧底】(词【{my_word}】)，性格【{personality}】。
                    目标：谨慎苟活，随大流。
                    历史与裁判报告已给出。
                    思维链：观察局势。如果有明显的平民攻击目标，就跟随投票。如果局势完美，为了不暴露自己，优先选择弃票。
                    输出：投票`名字|理由` 或 `弃票|理由`
                    """
            else:
                # 平民
                if personality == PERSONALITY_RADICAL:
                    # === 激进派平民：眼里揉不得沙子 ===
                    prompt = f"""
                    你是{p}，【平民】(词【{my_word}】)，性格【{personality}】。
                    目标：宁可错杀，不可放过。对低分、划水或反常识行为零容忍。
                    历史与裁判报告已给出。
                    思维链：紧盯裁判报告和历史发言。只要有人分数低、评语差，或者有任何反常识的描述，立刻投票攻击！绝不接受弃票这种懦弱行为。
                    输出：`被投人名字|严厉的简短理由`
                    """
                else:
                    # === 理智派平民：平衡分析 ===
                    prompt = f"""
                    你是{p}，【平民】(词【{my_word}】)，性格【{personality}】。
                    目标：理智分析，稳妥找出卧底。
                    历史与裁判报告已给出。
                    思维链：综合分析裁判分数、评语和行为逻辑。优先投给有明显疑点的人。如果全员高分且无破绽，理智选择弃票，避免误伤。
                    输出：投票`名字|理由` 或 `弃票|理由`
                    """

            # AI 执行推理和投票
            try:
                res = llm.invoke([HumanMessage(content=prompt)])
                content = res.content.strip()
                reason = "无理由"
                raw_target = content
                vote_target = raw_target
                if "|" in content:
                    parts = content.split("|", 1)
                    vote_target = parts[0].strip()
                    reason = parts[1].strip()
                    raw_target = vote_target

                if vote_target == "弃票":
                    print(f"   🤖 [{p}]({personality}) 选择 -> 弃票 (理由: {reason})")
                    vote_log.append(f"[{p}] 选择弃票 (理由: {reason})")
                else:
                    found = False
                    for valid in players:
                        if valid in vote_target and valid != p:
                            vote_target = valid
                            found = True
                            break
                    if not found:
                        print(f"   ⚠️ [{p}] 推理目标【{raw_target}】无效，被迫弃票。")
                        vote_target = "弃票"
                        vote_log.append(f"[{p}] 被迫弃票 (推理无效)。")
                    else:
                        print(f"   🤖 [{p}]({personality}) 投给了 -> {vote_target} (理由: {reason})")
                        vote_log.append(f"[{p}] 投给了 {vote_target} (理由: {reason})")
            except Exception as e:
                print(f"   ⚠️ [{p}] 投票程序出错: {e}，被迫弃票。")
                vote_target = "弃票"
                vote_log.append(f"[{p}] 程序错误弃票。")

        new_votes[p] = vote_target
    print("-" * 30)

    # 【核心更新】将本轮投票记录添加到历史中，供下一轮参考
    round_log_str = f"--- 第 {state['round_count']} 轮投票结果 ---\n" + "\n".join(
        vote_log) + "\n-----------------------"
    new_history = state["history"] + [round_log_str]

    return {"votes": new_votes, "history": new_history}


def judge_node(state: GameState):
    """判决节点 V8.0 (4人局，一票定生死)"""
    # 1. 作弊检查 (不变)
    if state.get("cheater_detected"):
        cheater = state["cheater_detected"]
        spy = state["spy_player"]
        if cheater == spy:
            return {"game_result": f"卧底[{cheater}]撒谎作弊被抓！平民直接胜利！🎉"}
        else:
            return {"game_result": f"平民[{cheater}]撒谎作弊被抓！这局算平民输！😈"}

    # 2. 正常计票
    votes = state["votes"]
    counts = {}
    for target in votes.values():
        if target != "弃票": counts[target] = counts.get(target, 0) + 1
    print(f"📊 最终票数统计: {counts}")

    # 如果没人投票
    if not counts:
        print("⚖️ 无人有效投票，进入下一轮！\n")
        return {"round_count": state["round_count"] + 1, "current_turn_idx": 0}

    # 排序找出最高票
    sorted_votes = sorted(counts.items(), key=lambda x: x[1], reverse=True)
    top_votes = sorted_votes[0][1]
    top_players = [p for p, v in sorted_votes if v == top_votes]

    # 3. 平票检测
    if len(top_players) > 1:
        print(f"⚖️ 平票！{top_players}，无人出局，进入下一轮！\n")
        return {"round_count": state["round_count"] + 1, "current_turn_idx": 0}

    # 4. 淘汰结算 (一票定生死)
    out_player = top_players[0]
    print(f"\n❌ [{out_player}] 被公投淘汰出局！")

    spy = state["spy_player"]
    print(f"🔑 游戏结束！真相揭晓：")
    print(f"   卧底是 [{spy}]，词是【{state['player_words'][spy]}】")
    common_word = state['player_words'][HUMAN_NAME] if spy != HUMAN_NAME else state['player_words']['AI_1号']
    print(f"   平民词是【{common_word}】")

    # 判定胜负
    if out_player == spy:
        result = f"\n🎉 恭喜！成功抓出卧底 [{spy}]！平民阵营胜利！"
    else:
        result = f"\n😈 糟糕！平民 [{out_player}] 被误杀！卧底 [{spy}] 获得胜利！"

    # 游戏直接结束
    return {"game_result": result}


# ---------------- 3. 构建图与路由 (保持不变) ----------------
def turn_router(state: GameState):
    if state.get("cheater_detected"): return "judge"
    if state["current_turn_idx"] < len(state["players"]): return "speak"
    return "analyze"


def game_router(state: GameState):
    if state.get("game_result"): return "end"
    return "continue"


workflow = StateGraph(GameState)
workflow.add_node("Init", init_game_node)
workflow.add_node("Speaker", speaker_node)
workflow.add_node("FactCheck", fact_check_node)
workflow.add_node("LLMScoring", llm_scoring_node)
workflow.add_node("Voting", voting_node)
workflow.add_node("Judge", judge_node)

workflow.set_entry_point("Init")
workflow.add_edge("Init", "Speaker")
workflow.add_edge("Speaker", "FactCheck")
workflow.add_conditional_edges("FactCheck", turn_router,
                               {"speak": "Speaker", "analyze": "LLMScoring", "judge": "Judge"})
workflow.add_edge("LLMScoring", "Voting")
workflow.add_edge("Voting", "Judge")
workflow.add_conditional_edges("Judge", game_router, {"continue": "Speaker", "end": END})
app = workflow.compile()

# ---------------- 4. 启动游戏 ----------------
if __name__ == "__main__":
    try:
        app.invoke({"players": []})
    except KeyboardInterrupt:
        print("\n🛑 游戏已手动终止。")
    except Exception as e:
        import traceback

        traceback.print_exc()
